/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
/*
 * main.c
 * Copyright (C) Alexandru 2007 <alextud@gmail.com>
 * 
 * main.c is free software.
 * 
 * You may redistribute it and/or modify it under the terms of the
 * GNU General Public License, as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option)
 * any later version.
 * 
 * main.c is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with main.c.  If not, write to:
 * 	The Free Software Foundation, Inc.,
 * 	51 Franklin Street, Fifth Floor
 * 	Boston, MA  02110-1301, USA.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <glib.h>
#include <glib/gprintf.h>
#include <glib/gstdio.h>
#include <math.h>
#include <stdlib.h>
#include <cairo.h>
#include <gdk-pixbuf/gdk-pixbuf.h>

//default value
gint WIDTH = 250;
gchar *WIDTH_STR = "250";
gint HEIGHT = -1;
gchar *HEIGHT_STR = NULL;
gboolean DISABLE_TIME = FALSE;
gint ROWS = 8;
gint COLUMNS = 4;
gchar *PATH = NULL;
gulong MIN_SIZE = 100 * 1024 * 1024; // > 100 MB

gchar *TEMP_DIR = NULL;
//gchar *OLD_DIR = NULL;
GPid CHILD_PID;
GIOChannel *INPUT_CH;
GIOChannel *OUTPUT_CH;
GList *movie_list = NULL;



gboolean wait_for_mplayer_to_load ()
{
	GError *error = NULL;
	gchar *temp = NULL;
	
//	do {
//		g_io_channel_read_line (OUTPUT_CH, &temp, NULL, NULL, &error);
//		if (! temp)
//			continue;
//	}while (! g_str_has_prefix (temp, "Compiled"));
	
	gint i;
	for (i = 0; i < 1; i++)//only first line
	{
		g_io_channel_read_line (OUTPUT_CH, &temp, NULL, NULL, &error);
		g_free (temp);
	}
	
	g_print ("\n mplayer loaded");
	return TRUE;
}

gboolean mplayer_spawn ()
{
	gchar *argvm[22];
	
	argvm[0] = "mplayer";
	argvm[1] = "-identify";
	argvm[2] = "-nojoystick";
	argvm[3] = "-nolirc";
	argvm[4] = "-nocache";
	argvm[5] = "-nortc";
	argvm[6] = "-noautosub";
	argvm[7] = "-ao";
	argvm[8] = "null";
	argvm[9] = "-vo";
	argvm[10] = "png";
	argvm[11] = "-slave";
	argvm[12] = "-idle";
	argvm[13] = "-benchmark"; //?
	argvm[14] = "-zoom";
	if (HEIGHT > 1)
	{
		argvm[15] = "-x";
		argvm[16] = WIDTH_STR;
		argvm[17] = "-y";
		argvm[18] = HEIGHT_STR;
		argvm[19] = NULL;
	}
	else
	{
		argvm[15] = "-xy";
		argvm[16] = WIDTH_STR;
		argvm[17] = NULL;
	}
	
	GError *error = NULL;
	gint input_fd;
	gint output_fd;
	
	g_spawn_async_with_pipes (TEMP_DIR,
							  argvm, NULL,
							  G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL,
							  NULL, NULL,
							  &CHILD_PID, 
							  &input_fd, &output_fd, NULL,
							  &error);
	
	if (error)	return FALSE;
	
	//asteptam 20 ms
	//g_usleep (20*1000);
	
	INPUT_CH = g_io_channel_unix_new (input_fd);
	OUTPUT_CH = g_io_channel_unix_new (output_fd);
	
	return TRUE;
}

void add_movies_from_directory (gchar *directory) 
{
	GError *error = NULL;
	GDir *dir = g_dir_open (directory, 0, &error);
	if (error)		return;
	
	//g_print ("\n directorul %s", directory);
	
	struct stat buf;
	gchar *filename = g_dir_read_name (dir);
	
	for (; filename; filename = g_dir_read_name (dir))
	{
		filename = g_strconcat (directory, "/", filename, NULL);
		
		if (g_file_test (filename, G_FILE_TEST_IS_DIR))
			add_movies_from_directory (filename);
		
		if (g_str_has_suffix (filename, ".avi") || g_str_has_suffix (filename, ".mov")
			|| g_str_has_suffix (filename, ".wmv") || g_str_has_suffix (filename, ".mpg")
			|| g_str_has_suffix (filename, "mpeg") || g_str_has_suffix (filename, ".mkv"))
		{		
			stat (filename, &buf);
			
			if (buf.st_size > MIN_SIZE ) // > 100 MB
			{
				movie_list = g_list_prepend (movie_list,
											 g_filename_to_utf8 (filename, 
																 -1,
																 NULL, NULL, NULL));
				//g_print ("\n adaugam filmul %s", filename);
			}
		}
		
		g_free (filename);
	}
	
	g_dir_close (dir);
}

gboolean parse_input (int argc, char *argv[])
{
	if (argc < 2)
		return FALSE;
	
	int i;
	for (i = 1; i < argc; i++)
	{
		if (g_str_has_prefix (argv[i], "-w")) //width
		{
			if (argc <= i + 1)	return FALSE;
			WIDTH_STR = g_strdup (argv[++i]);
			WIDTH = atoi (argv[i]);
			continue;
		}
		
		if (g_str_has_prefix (argv[i], "-h")) //height
		{
			if (argc <= i + 1)	return FALSE;
			HEIGHT_STR = g_strdup (argv[++i]);
			HEIGHT = atoi (argv[i]);
			continue;
		}
			
		if (g_str_has_prefix (argv[i], "-n")) //notime
		{
			DISABLE_TIME = TRUE;
			continue;
		}
		
		if (g_str_has_prefix (argv[i], "-r"))//rows
		{
			if (argc <= i + 1)	return FALSE;
			ROWS = atoi (argv[++i]);
			continue;
		}
		
		if (g_str_has_prefix (argv[i], "-c"))//columns
		{
			if (argc <= i + 1)	return FALSE;
			COLUMNS = atoi (argv[++i]);
			continue;
		}
		
		if (g_str_has_prefix (argv[i], "-p"))//path
		{
			if (argc <= i + 1)	return FALSE;

			if (g_path_is_absolute (argv[++i]))
				PATH = g_strdup (argv[i]);
			else
				PATH = g_strconcat (g_get_current_dir (), "/",
													  argv[i], NULL);
			continue;
		}
		
		if (g_str_has_prefix (argv[i], "-d"))//dir
		{
			if (argc <= i + 1)	return FALSE;
			add_movies_from_directory (argv[++i]);
			continue;
		}
		
		if (g_str_has_prefix (argv[i], "-s"))//minimum size for movie
		{
			if (argc <= i + 1)	return FALSE;
			MIN_SIZE = atoi (argv[++i]) * 1024 * 1024;
			continue;
		}
		
		if (g_path_is_absolute (argv[i]))
			movie_list = g_list_prepend (movie_list, argv[i]);
		else
			movie_list = g_list_prepend (movie_list, 
										 g_strconcat (g_get_current_dir (), "/",
													  argv[i], NULL));
	}

	return TRUE;
}


void show_help ()
{
	g_print ("\nUsage : mplayer-snapshot [options] filename");
	g_print ("\n -w	<width value>");
	g_print ("\n -h	<height value>");
	g_print ("\n -n				do not show time");
	g_print ("\n -r	<rows>			number of rows");	
	g_print ("\n -c	<columns>		number of colums");
	g_print ("\n -p	<path>			where to save images");
	g_print ("\n -d	<directory>		for all movies from directory\n");
	g_print ("\n -s <value>			minimum size in MB\n");
}

void mplayer_load_movie (gchar *movie)
{
	gchar *command = NULL;
	
	command = g_strconcat ("load \"", movie, "\"\npause\n", NULL);
//	g_print ("\ncommand %s", command);
	g_io_channel_write_chars (INPUT_CH, command, -1, NULL, NULL);
	g_io_channel_flush (INPUT_CH, NULL);
	
	gint i, nr_images = ROWS * COLUMNS;
	gfloat step = (float) 100 / (nr_images + 2);
	
	for (i = 0; i < nr_images; i++)
	{
		sprintf (command, "seek %2f 1\npause\n", step * (i + 1));
		g_io_channel_write_chars (INPUT_CH, command, -1, NULL, NULL);
		g_io_channel_flush (INPUT_CH, NULL);
	}
	
	g_free (command);
}


	gfloat *time_position;
	enum {id = 0, value};
	enum {length = 0, width, height, bitrate};
	gchar *movie_info[10][2] = {
		{"ID_LENGTH", NULL },
		{"ID_VIDEO_WIDTH", NULL },
		{"ID_VIDEO_HEIGHT", NULL },
		{"ID_VIDEO_BITRATE", NULL },
		{"ID_VIDEO_CODEC", NULL },
		{"ID_AUDIO_CODEC", NULL },
		{"ID_VIDEO_FPS", NULL },
		{"ID_VIDEO_FORMAT", NULL}, 
		{"ID_AUDIO_FORMAT", NULL }
	};

	enum {size, resolution, duration};
	gchar *info_name[10] = {
			"File Size: ", "Resolution: ", "Duration: ",
			"Bitrate: ", "Video Codec: ", "Audio Codec: ",
			"Frame Rate: ", "Video Format: ", "Audio Format: "
	};

void parse_mplayer_output ()
{
	GError *error = NULL;
	gchar *temp = NULL;
	gfloat buf = 0;
	
	int i;
	int pos = 0;
	int nr_images = ROWS * COLUMNS;
	time_position = g_new (gfloat, nr_images + 1);
	
	//----------------identify------------------
	int k;
	for (k = 0; k < 9; )
	{
		temp = NULL;
		error = NULL;
		g_io_channel_read_line (OUTPUT_CH, &temp, NULL, NULL, &error);
		
		if (error) //error on conversion 
		{
			g_print ("\n error 'identify'  %s", error->message);
			error = NULL;
			//just changing conversion to null, it works
			g_io_channel_set_encoding (OUTPUT_CH, NULL, &error);
			continue;
		}
		
		if (! temp)	continue;
		for (i = 0; i < 9; i++)
			if (g_str_has_prefix (temp, movie_info[i][id]))
		{
			//avem \n la sf liniei
			int len = strlen (movie_info[i][id]);
			int len1 = strlen (temp);
			
			movie_info[i][value] = g_strndup (temp + len + 1, len1 - len - 2);//scapam de =
			g_print ("\n %s : %s", movie_info[i][id], movie_info[i][value]);
			k++;
			break;
		}
		g_free (temp);
		
	}
	
	//--------------------------time_position---------------
	for (pos = 0; pos < nr_images; )//pana citim cele 32 time_position pt fiecare frame
	{			
		temp = NULL;
		g_io_channel_read_line (OUTPUT_CH, &temp, NULL, NULL, &error);
		
		if (error)
			g_print ("\n error 'time position'  : %s", error->message);
		
		if (! temp)	continue;
		
		//g_print (temp);
		
		//g_print ("\n pos  %d", pos);
		
		if (g_str_has_prefix (temp, "A:"))
		{
			time_position[pos] = buf;
			sscanf (temp, "A:%*f V:%f", &buf);
			
			if (buf - time_position[pos] > 2) //seems buggy
				time_position[pos++] = buf;
		}
		
		g_free (temp);
	}
}

gchar **create_info (gchar *movie_name)
{
	int i;
	
	//copiem incepand de la bitrate restul
	gchar **info = g_new (gchar *, 11);
	for (i = bitrate + 1; i < 9; i++)
		info[i] = g_strconcat (info_name[i], movie_info[i][value], NULL);


	
	//------ resolution -----
	info [resolution] = g_strconcat (info_name [resolution], 
									 movie_info [width][value],
									 "x", movie_info [height][value], NULL);
	if (! HEIGHT_STR)
		HEIGHT = WIDTH * atoi (movie_info[height][value]) / 
				atoi (movie_info[width][value]);
	
	
	//------- for duration ------
	gchar buffer[20];
	int hh, mm, ss;
	float movie_length = atof (movie_info[length][value]);
	ss = (int) movie_length % 60;
	mm = (int) movie_length / 60;
	mm = (int) mm % 60;
	hh = (int) movie_length / 3600;
	sprintf (buffer, "%02d:%02d:%02d", hh, mm, ss);
	info[duration] = g_strconcat (info_name[duration], buffer, NULL);
	
	
	//--------- for size -------
	struct stat buf;
	stat (movie_name, &buf);
	gfloat file_size = buf.st_size / (1024 * 1024);
	if (file_size > 1024)
		sprintf (buffer, "%.1f GB", file_size / 1024);
	else
		sprintf (buffer, "%.1f MB", file_size);
	info[size] = g_strconcat (info_name [size], buffer, NULL);
	
	//-------pt bitrate ----------
	sprintf (buffer, "%d Kbps", atoi (movie_info[bitrate][value]) / 1024);
	info [bitrate] = g_strconcat (info_name [bitrate], buffer, NULL);
	
	return info;
}

void 
get_frame_step_and_start (gint *nr_frames,gint *frame_step, gint *frame_start)
{
	gchar frame_name[15];
	gint nr_images = ROWS * COLUMNS;
	
	g_usleep (20*1000);
	
	sprintf (frame_name, "%08d.png", *nr_frames + nr_images * 2 + 2);
	if (g_file_test (frame_name, G_FILE_TEST_EXISTS))
	{
		*frame_step = 2;
		*frame_start = 4 + *nr_frames;
		*nr_frames += nr_images * 2 + 2;
		return;
	}
	
	sprintf (frame_name, "%08d.png", *nr_frames + nr_images * 2 + 1);
	if (g_file_test (frame_name, G_FILE_TEST_EXISTS))
	{
		*frame_step = 2;
		*frame_start = 3 + *nr_frames;
		*nr_frames += nr_images * 2 + 1;
		return;
	}
	
	sprintf (frame_name, "%08d.png", *nr_frames + nr_images * 2 );
	if (g_file_test (frame_name, G_FILE_TEST_EXISTS))
	{
		*frame_step = 2;
		*frame_start = 2 + *nr_frames;
		*nr_frames += nr_images * 2;
		return;
	}
	
	sprintf (frame_name, "%08d.png", *nr_frames + nr_images + 1);
	if (g_file_test (frame_name, G_FILE_TEST_EXISTS))
	{
		*frame_step = 1;
		*frame_start = 2 + *nr_frames;
		*nr_frames += nr_images + 1;
		return;
	}

	*frame_step = 1;
	*frame_start = 1 + *nr_frames;
	*nr_frames += nr_images ;
	
}


void 
create_png_with_cairo (gchar *movie_name, gchar **info, 
					   gint frame_start, gint frame_step)
{
	int i,j, k;
	
	gint x_offset = 5;
	gint y_offset = 75;
	
	
	cairo_surface_t *image;
	gint image_width = (WIDTH + x_offset) * COLUMNS + 4;
	gint image_height = (HEIGHT + x_offset) * ROWS + y_offset;
	image = cairo_image_surface_create (CAIRO_FORMAT_RGB24, 
										image_width,
										image_height);
	
	cairo_t *cr = cairo_create (image);
	
	//stergem background
	cairo_set_source_rgb (cr, 1.0f, 1.0f, 1.0f);
	cairo_paint (cr);
	
	
	//--------scriem numele filmului------
    cairo_font_options_t* font;
    
    font = cairo_font_options_create ();
    cairo_font_options_set_antialias (font, CAIRO_ANTIALIAS_SUBPIXEL);
	//cairo_font_options_set_antialias (font, CAIRO_ANTIALIAS_GRAY);
    cairo_set_font_options (cr, font);
	
	cairo_set_source_rgb (cr, 0.0f, 0.0f, 0.0f);
    cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL,
						   CAIRO_FONT_SLANT_ITALIC);
    cairo_set_font_size(cr, 18);
	
	cairo_text_extents_t extents;
	movie_name = g_strconcat ("File Name: ", movie_name, NULL);
	cairo_text_extents (cr, movie_name, &extents);

	cairo_move_to (cr, image_width / 2 - extents.width / 2, y_offset / 3 - 5);
	cairo_show_text (cr, movie_name);
	
	
	//scriem si restul proprietatilor
	cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL,
						   CAIRO_FONT_WEIGHT_NORMAL);
	gint font_size = 14;
	do { //calculam dimensiunea fontului
		cairo_set_font_size (cr, font_size);
		cairo_text_extents (cr, "Resolution: 1000x1000", &extents);
		font_size-=2;
	}while (extents.width * 3 + x_offset > image_width);
	
	for (k = 0, i = 0; i < 3; i++)
		for (j = 0; j < 3 ; j++, k++)
	{
		cairo_move_to (cr, i * (image_width / 3) + x_offset,
					   j * (extents.height + 5) + 15 + y_offset / 3);
		cairo_show_text (cr, info[k]);
	}

	cairo_surface_t *temp_image;
	gchar filename[15];
	gint frame_pos;
	
	gint hh, mm, ss;
	gchar temp[20];
	
	gint text_length = 0;
	cairo_text_extents (cr, "00:00:000", &extents);
	text_length = extents.width;
	
	
	//------------load images --------
	for (j = 0, k = 0, frame_pos = frame_start; j < ROWS; j++)
		for (i = 0; i < COLUMNS; i++, frame_pos += frame_step, k++)
	{
		sprintf (filename, "%08d.png", frame_pos);
	
		temp_image = cairo_image_surface_create_from_png (filename);
		cairo_set_source_surface (cr, temp_image,
								  i * (WIDTH + x_offset) + x_offset,
					   			  j * (HEIGHT + x_offset) + y_offset);
		
		cairo_paint (cr);
		cairo_surface_destroy (temp_image);
		
						//-----write time
		if (! DISABLE_TIME)
		{
			ss = (int) time_position[k] % 60;
			mm = (int) time_position[k] / 60;
			mm = (int) mm % 60;
			hh = (int) time_position[k] / 3600;
			sprintf (temp, "%02d:%02d:%02d", hh, mm, ss);

			cairo_move_to (cr, 
						   i * (WIDTH + x_offset) + x_offset + WIDTH - extents.width,
						   j * (HEIGHT + x_offset) + y_offset + HEIGHT - 5);
			
			cairo_set_source_rgb (cr, 0.9f, 0.9f, 0.9f);
			cairo_show_text (cr, temp);
		}


	}
	
	cairo_surface_write_to_png (image, "image.png");
	cairo_surface_destroy (image);
	cairo_destroy (cr);	
	cairo_font_options_destroy(font);
}

void create_jpg (gchar *movie_path)
{
	GdkPixbuf *pixbuf = NULL;
	GError *error = NULL;
	pixbuf = gdk_pixbuf_new_from_file ("image.png", &error);
	if (error)
		g_print ("failed to load image.png");
	
	gchar *image_path;
	gchar *movie_name;
	gchar *image_name;
	
	movie_name = g_path_get_basename (movie_path);
	image_name = g_strconcat (g_strndup (movie_name, strlen (movie_name) - 3),
								  "jpg", NULL);
	
	if (PATH)
		image_path = g_strconcat (PATH, "/", image_name, NULL);
	else
		image_path = g_strconcat (g_path_get_dirname (movie_path), "/",
								  image_name, NULL);
//		image_path = g_strconcat (OLD_DIR, "/", 
//								  image_name, NULL);

	gdk_pixbuf_save (pixbuf, image_path, "jpeg", &error,
                 "quality", "85", NULL);
	
	if (error)
		g_print ("failed to save %s\n%s\n", image_path, error->message);
	
	gdk_pixbuf_unref (pixbuf);
	
	g_free (image_path);
	g_free (movie_name);
	g_free (image_name);
}

void remove_frames (gint frame_start, gint nr_frames)
{
	gchar filename[15];
	int i = 0;
	for (i = frame_start - 4; i <= nr_frames; i++)
	{
		sprintf (filename, "%08d.png", i);
		remove (filename);
	}
	
	remove ("image.png");
}

int
main (int argc, char *argv[])
{
	if (! parse_input (argc, argv))
	{
		show_help ();
		return 1;
	}

	TEMP_DIR = mkdtemp (g_strconcat (g_get_tmp_dir (),
								   "/mplayer-snapshot.XXXXXX", NULL));
	g_chdir (TEMP_DIR);
	
	g_type_init();
	
	if (! mplayer_spawn ())
		return 2;
	
	if (! wait_for_mplayer_to_load ())
		return 3;
	
	gchar *movie;
	GList *list;
	gchar **info;
	gint nr_frames = 0, frame_start, frame_step;
	for (list = movie_list; list; list = list->next)
	{
		movie = list->data;
		mplayer_load_movie (movie);
		g_print ("\nmovie %s", movie);
		parse_mplayer_output ();
		info = create_info (movie);
		get_frame_step_and_start (&nr_frames, &frame_step, &frame_start);
		g_print ("\n frame start %d\n frame step %d\n number of frames %d\n",
				 frame_start, frame_step, nr_frames);
		create_png_with_cairo (g_path_get_basename (movie), 
							   info, frame_start, frame_step);
		create_jpg (movie);
		remove_frames (frame_start, nr_frames);
		g_free (info);
	}
	
	g_io_channel_write_chars (INPUT_CH, g_strdup ("quit\n"), -1, NULL, NULL);
	g_io_channel_flush (INPUT_CH, NULL);
	
	g_usleep (200 * 1000);//waiting mplayer to exit (without error)
	
	g_io_channel_shutdown (INPUT_CH, TRUE, NULL);
	g_io_channel_shutdown (OUTPUT_CH, TRUE, NULL);

	remove (TEMP_DIR);
	
	//g_spawn_close_pid (CHILD_PID);
	
	
	return 0;
}
